#region Using directives
using System.Collections.Generic;
using SharpWind.Resources.Shaders;
using Microsoft.DirectX;
using SharpWind.Graphics;
#endregion

namespace SharpWind.Scene
{
	public class SceneManager
	{
		#region Fields

		private Dictionary<string, SceneObject> objects = new Dictionary<string, SceneObject>();

		private Dictionary<string, Camera> cameras = new Dictionary<string, Camera>();

		private Camera activeCamera = null;

		private Shader shader = null;

		private Shader shadowMapShader = null;

		private Vector4 lightPosition = Vector4.Empty;

		private Matrix lightViewProjection = Matrix.Identity;

		private float lightPower = 0f;

		#endregion

		#region Constructor
		
		public SceneManager()
		{
			this.shadowMapShader = Engine.Instance.Shaders.Load( "ShadowMap.fx" );
			this.shadowMapShader.Technique = "ShadowMap";
			this.shadowMapShader.SetValue( "maxDepth", 60f );
		}

		#endregion

		#region Methods

		public void AddObject( string name, SceneObject item )
		{
			if( !string.IsNullOrEmpty( name ) && !this.objects.ContainsKey( name ) )
			{
				this.objects.Add( name, item );
			}
		}

		public void RemoveObject( string name )
		{
			if( !string.IsNullOrEmpty( name ) && this.objects.ContainsKey( name ) )
			{
				this.objects.Remove( name );
			}
		}

		public SceneObject GetObject( string name )
		{
			if( !string.IsNullOrEmpty( name ) && this.objects.ContainsKey( name ) )
			{
				return this.objects[name];
			}

			return null;
		}

		public void ClearObjects()
		{
			this.objects.Clear();
		}

		public void AddCamera( string name, Camera camera )
		{
			if( !string.IsNullOrEmpty( name ) && !this.cameras.ContainsKey( name ) )
			{
				this.cameras.Add( name, camera );
			}
		}

		public void RemoveCamera( string name )
		{
			if( !string.IsNullOrEmpty( name ) && this.cameras.ContainsKey( name ) )
			{
				this.cameras.Remove( name );
			}
		}

		public Camera GetCamera( string name )
		{
			if( !string.IsNullOrEmpty( name ) && this.cameras.ContainsKey( name ) )
			{
				return this.cameras[name];
			}

			return null;
		}

		public void ClearCameras()
		{
			this.cameras.Clear();
		}

		internal void Update( float elapsedTime )
		{
			this.activeCamera.Update();
			Engine.Instance.Graphics.ViewMatrix			= this.activeCamera.ViewMatrix;
			Engine.Instance.Graphics.ProjectionMatrix	= this.activeCamera.ProjectionMatrix;

			foreach( SceneObject sceneObject in this.objects.Values )
			{
				sceneObject.Update( elapsedTime );
			}
		}

		internal void Render()
		{
			UpdateLights();
			GenerateShadowMap();
		}

		private void UpdateLights()
		{
			this.lightViewProjection = Matrix.LookAtLH( new Vector3( this.lightPosition.X, 
																	 this.lightPosition.Y,
																	 this.lightPosition.Z ), 
										this.activeCamera.LookAt, new Vector3( 0, 0, 1 ) ) *
										this.activeCamera.ProjectionMatrix;
			this.shadowMapShader.SetValue( "lightWorldViewProjection", lightViewProjection );
		}

		private void GenerateShadowMap()
		{
			int numPasses = this.shadowMapShader.Begin();
			for( int i = 0; i < numPasses; i++ )
			{
				this.shadowMapShader.BeginPass( i );
				RenderObjects();
				this.shadowMapShader.EndPass();
			}
		}

		private void RenderObjects()
		{
			foreach( SceneObject sceneObject in this.objects.Values )
			{
				StaticObject staticObject = sceneObject as StaticObject;
				if( staticObject != null )
				{
					this.shadowMapShader.SetValue( "lightWorldViewProjection", staticObject.WorldMatrix * this.lightViewProjection );
					this.shadowMapShader.Commit();

					if( staticObject.NormalMapping )
					{
						Engine.Instance.Graphics.Device.VertexDeclaration = VertexDeclarations.NormalMapping;

						Matrix viewProjection = staticObject.WorldMatrix * this.activeCamera.ViewMatrix *
												this.activeCamera.ProjectionMatrix;

						Matrix viewInverseTranspose = viewProjection;
						viewInverseTranspose = Matrix.Invert( viewInverseTranspose );
						viewInverseTranspose = Matrix.TransposeMatrix( viewInverseTranspose );

						this.shader = Engine.Instance.Shaders.Load( "NormalMapping.fx" );
						this.shader.Technique = "NormalMapping";
						this.shader.SetValue( "viewProjection", viewProjection );
						this.shader.SetValue( "viewInverseTranspose", viewInverseTranspose );
						this.shader.SetValue( "world", staticObject.WorldMatrix );
						this.shader.SetValue( "lightPosition", this.lightPosition );
					}
					else
					{
						Engine.Instance.Graphics.Device.VertexDeclaration = VertexDeclarations.TransformTexturing;

						Matrix worldViewProjection = sceneObject.WorldMatrix * this.activeCamera.ViewMatrix *
													 this.activeCamera.ProjectionMatrix;

						Matrix rotation =
							Matrix.RotationYawPitchRoll( sceneObject.RotationY, sceneObject.RotationX, sceneObject.RotationZ );

						this.shader = Engine.Instance.Shaders.Load( "BasicShader.fx" );
						this.shader.Technique = "TransformAndTexturing";
						this.shader.SetValue( "rotation", rotation );
						this.shader.SetValue( "worldViewProjection", worldViewProjection );
						this.shader.SetValue( "lightPosition", this.lightPosition );
						this.shader.SetValue( "lightPower", this.lightPower );
					}

					int numPasses = this.shader.Begin();
					for( int i = 0; i < numPasses; i++ )
					{
						this.shader.BeginPass( i );
						staticObject.Render( this.shader );
						this.shader.EndPass();
					}
					this.shader.End();
				}
			}
		}

		#endregion

		#region Properties

		public Camera ActiveCamera
		{
			get
			{
				return this.activeCamera;
			}
			internal set
			{
				if( this.activeCamera != value )
				{
					if( this.activeCamera != null )
					{
						this.activeCamera.IsActive = false;
					}
					
					value.IsActive				= true;
					this.activeCamera			= value;
					this.activeCamera.Update();
				}
			}
		}

		public Vector3 LightPosition
		{
			get
			{
				return new Vector3( this.lightPosition.X, this.lightPosition.Y, this.lightPosition.Z );
			}
			set
			{
				this.lightPosition = new Vector4( value.X, value.Y, value.Z, 1 );
			}
		}

		public float LightPower
		{
			get
			{
				return this.lightPower;
			}
			set
			{
				this.lightPower = value;
			}
		}

		#endregion
	}
}
